\subsubsection{std::string}
\myindex{\Cpp!STL!std::string}
\label{std_string}

\myparagraph{Как устроена структура}

Многие строковые библиотеки \InSqBrackets{\CNotes 2.2} обеспечивают структуру содержащую ссылку 
на буфер собственно со строкой, переменная всегда содержащую длину строки 
(что очень удобно для массы функций \InSqBrackets{\CNotes 2.2.1}) и переменную содержащую текущий размер буфера.

Строка в буфере обыкновенно оканчивается нулем: это для того чтобы указатель на буфер можно было
передавать в функции требующие на вход обычную сишную \ac{ASCIIZ}-строку.

Стандарт \Cpp не описывает, как именно нужно реализовывать std::string,
но как правило они реализованы как описано выше, с небольшими дополнениями.

Строки в \Cpp это не класс (как, например, QString в Qt), а темплейт (basic\_string), 
это сделано для того чтобы поддерживать 
строки содержащие разного типа символы: как минимум \Tchar и \IT{wchar\_t}.

Так что, std::string это класс с базовым типом \Tchar.

А std::wstring это класс с базовым типом \IT{wchar\_t}.

\mysubparagraph{MSVC}

В реализации MSVC, вместо ссылки на буфер может содержаться сам буфер (если строка короче 16-и символов).

Это означает, что каждая короткая строка будет занимать в памяти по крайней мере $16 + 4 + 4 = 24$ 
байт для 32-битной среды либо $16 + 8 + 8 = 32$ 
байта в 64-битной, а если строка длиннее 16-и символов, то прибавьте еще длину самой строки.

\lstinputlisting[caption=пример для MSVC,style=customc]{\CURPATH/STL/string/MSVC_RU.cpp}

Собственно, из этого исходника почти всё ясно.

Несколько замечаний:

Если строка короче 16-и символов, 
то отдельный буфер для строки в \glslink{heap}{куче} выделяться не будет.

Это удобно потому что на практике, основная часть строк действительно короткие.
Вероятно, разработчики в Microsoft выбрали размер в 16 символов как разумный баланс.

Теперь очень важный момент в конце функции main(): мы не пользуемся методом c\_str(), тем не менее,
если это скомпилировать и запустить, то обе строки появятся в консоли!

Работает это вот почему.

В первом случае строка короче 16-и символов и в начале объекта std::string (его можно рассматривать
просто как структуру) расположен буфер с этой строкой.
\printf трактует указатель как указатель на массив символов оканчивающийся нулем и поэтому всё работает.

Вывод второй строки (длиннее 16-и символов) даже еще опаснее: это вообще типичная программистская ошибка 
(или опечатка), забыть дописать c\_str().
Это работает потому что в это время в начале структуры расположен указатель на буфер.
Это может надолго остаться незамеченным: до тех пока там не появится строка 
короче 16-и символов, тогда процесс упадет.

\mysubparagraph{GCC}

В реализации GCC в структуре есть еще одна переменная --- reference count.

Интересно, что указатель на экземпляр класса std::string в GCC указывает не на начало самой структуры, 
а на указатель на буфера.
В libstdc++-v3\textbackslash{}include\textbackslash{}bits\textbackslash{}basic\_string.h 
мы можем прочитать что это сделано для удобства отладки:

\begin{lstlisting}
   *  The reason you want _M_data pointing to the character %array and
   *  not the _Rep is so that the debugger can see the string
   *  contents. (Probably we should add a non-inline member to get
   *  the _Rep for the debugger to use, so users can check the actual
   *  string length.)
\end{lstlisting}

\href{http://go.yurichev.com/17085}{исходный код basic\_string.h}

В нашем примере мы учитываем это:

\lstinputlisting[caption=пример для GCC,style=customc]{\CURPATH/STL/string/GCC_RU.cpp}

Нужны еще небольшие хаки чтобы сымитировать типичную ошибку, которую мы уже видели выше, из-за
более ужесточенной проверки типов в GCC, тем не менее, printf() работает и здесь без c\_str().

\myparagraph{Чуть более сложный пример}

\lstinputlisting[style=customc]{\CURPATH/STL/string/3.cpp}

\lstinputlisting[caption=MSVC 2012,style=customasmx86]{\CURPATH/STL/string/3_MSVC_RU.asm}

Собственно, компилятор не конструирует строки статически: да в общем-то и как
это возможно, если буфер с ней нужно хранить в \glslink{heap}{куче}?

Вместо этого в сегменте данных хранятся обычные \ac{ASCIIZ}-строки, а позже, во время выполнения, 
при помощи метода \q{assign}, конструируются строки s1 и s2
.
При помощи \TT{operator+}, создается строка s3.

Обратите внимание на то что вызов метода c\_str() отсутствует,
потому что его код достаточно короткий и компилятор вставил его прямо здесь:
если строка короче 16-и байт, то в регистре EAX остается указатель на буфер,
а если длиннее, то из этого же места достается адрес на буфер расположенный в \glslink{heap}{куче}.

Далее следуют вызовы трех деструкторов, причем, они вызываются только если строка длиннее 16-и байт:
тогда нужно освободить буфера в \glslink{heap}{куче}.
В противном случае, так как все три объекта std::string хранятся в стеке,
они освобождаются автоматически после выхода из функции.

Следовательно, работа с короткими строками более быстрая из-за м\'{е}ньшего обращения к \glslink{heap}{куче}.

Код на GCC даже проще (из-за того, что в GCC, как мы уже видели, не реализована возможность хранить короткую
строку прямо в структуре):

% TODO1 comment each function meaning
\lstinputlisting[caption=GCC 4.8.1,style=customasmx86]{\CURPATH/STL/string/3_GCC_RU.s}

Можно заметить, что в деструкторы передается не указатель на объект,
а указатель на место за 12 байт (или 3 слова) перед ним, то есть, на настоящее начало структуры.

\myparagraph{std::string как глобальная переменная}
\label{sec:std_string_as_global_variable}

Опытные программисты на \Cpp знают, что глобальные переменные \ac{STL}-типов вполне можно объявлять.

Да, действительно:

\lstinputlisting[style=customc]{\CURPATH/STL/string/5.cpp}

Но как и где будет вызываться конструктор \TT{std::string}?

На самом деле, эта переменная будет инициализирована даже перед началом \main.

\lstinputlisting[caption=MSVC 2012: здесь конструируется глобальная переменная{,} а также регистрируется её деструктор,style=customasmx86]{\CURPATH/STL/string/5_MSVC_p2.asm}

\lstinputlisting[caption=MSVC 2012: здесь глобальная переменная используется в \main,style=customasmx86]{\CURPATH/STL/string/5_MSVC_p1.asm}

\lstinputlisting[caption=MSVC 2012: эта функция-деструктор вызывается перед выходом,style=customasmx86]{\CURPATH/STL/string/5_MSVC_p3.asm}

\myindex{\CStandardLibrary!atexit()}
В реальности, из \ac{CRT}, еще до вызова main(), вызывается специальная функция,
в которой перечислены все конструкторы подобных переменных.
Более того: при помощи atexit() регистрируется функция, которая будет вызвана в конце работы программы:
в этой функции компилятор собирает вызовы деструкторов всех подобных глобальных переменных.

GCC работает похожим образом:

\lstinputlisting[caption=GCC 4.8.1,style=customasmx86]{\CURPATH/STL/string/5_GCC.s}

Но он не выделяет отдельной функции в которой будут собраны деструкторы: 
каждый деструктор передается в atexit() по одному.

% TODO а если глобальная STL-переменная в другом модуле? надо проверить.

